// Copyright (C) 2006 Igor Zelmanovich <izwebfilemanager@gmail.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Globalization;
using IZ.WebFileManager.Components;
using System.IO;
using System.Drawing;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;

namespace IZ.WebFileManager
{
	[SupportsEventValidation]
	[DefaultProperty ("Text")]
	[ToolboxData ("<{0}:FolderTree runat=\"server\"></{0}:FolderTree>")]
	public sealed class FolderTree : FileManagerControlBase, ICallbackEventHandler
	{
		#region Constructors

		public FolderTree ()
			: base () {
			InitTreeView ();
		}

		public FolderTree (FileManagerController controller)
			: base (controller) {
			InitTreeView ();
		}

		public FolderTree (FileManagerController controller, FileView fileFiew)
			: this (controller) {
			_fileView = fileFiew;
		}

		#endregion

		#region Fields
		FileView _fileView;

		bool stylesPrepared;
		Style hoverNodeStyle;
		TreeNodeStyle nodeStyle;
		TreeNodeStyle selectedNodeStyle;
		Collection<FolderNode> _nodes;
		Style controlLinkStyle;
		Style nodeLinkStyle;
		Style selectedNodeLinkStyle;
		Style hoverNodeLinkStyle;
		int _registeredStylesCounter = -1;
		Dictionary<Style, string> _registeredStyleClassNames;
		#endregion

		#region Events
		public EventHandler SelectedFolderChanged;
		#endregion

		#region Properties

		// TODO
		string SelectedPath {
			get { return null; }
		}

		[DefaultValue ("")]
		[Description ("The url of the image to show when a node can be collapsed.")]
		[UrlProperty]
		[Category ("Appearance")]
		string CollapseImageUrl {
			get {
				return (string) (ViewState ["CollapseImageUrl"] ?? String.Empty);
			}
			set {
				ViewState ["CollapseImageUrl"] = value;
			}
		}

		[DefaultValue ("")]
		[UrlProperty]
		[Description ("The url of the image to show when a node can be expanded.")]
		[Category ("Appearance")]
		string ExpandImageUrl {
			get {
				return (string) (ViewState ["ExpandImageUrl"] ?? String.Empty);
			}
			set {
				ViewState ["ExpandImageUrl"] = value;
			}
		}

		[PersistenceMode (PersistenceMode.InnerProperty)]
		[NotifyParentProperty (true)]
		[DefaultValue (null)]
		[Category ("Styles")]
		[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
		Style HoverNodeStyle {
			get {
				if (hoverNodeStyle == null) {
					hoverNodeStyle = new Style ();
					if (IsTrackingViewState)
						((IStateManager) hoverNodeStyle).TrackViewState ();
				}
				return hoverNodeStyle;
			}
		}

		[DefaultValue (20)]
		int NodeIndent {
			get {
				return (int) (ViewState ["NodeIndent"] ?? 20);
			}
			set {
				ViewState ["NodeIndent"] = value;
			}
		}

		Collection<FolderNode> Nodes {
			get {
				if (_nodes == null) {
					_nodes = new Collection<FolderNode> ();
				}
				return _nodes;
			}
		}

		[PersistenceMode (PersistenceMode.InnerProperty)]
		[NotifyParentProperty (true)]
		[DefaultValue (null)]
		[Category ("Styles")]
		[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
		TreeNodeStyle NodeStyle {
			get {
				if (nodeStyle == null) {
					nodeStyle = new TreeNodeStyle ();
					if (IsTrackingViewState)
						((IStateManager) nodeStyle).TrackViewState ();
				}
				return nodeStyle;
			}
		}

		[UrlProperty]
		[DefaultValue ("")]
		[Description ("The url of the image to show for leaf nodes.")]
		[Category ("Appearance")]
		string NoExpandImageUrl {
			get {
				return (string) (ViewState ["NoExpandImageUrl"] ?? String.Empty);
			}
			set {
				ViewState ["NoExpandImageUrl"] = value;
			}
		}

		char PathSeparator {
			get {
				return '/';
			}
		}

	    internal override string ClientScriptReference {
			get {
				if (_fileView != null)
					return FileManagerController.ClientScriptObjectNamePrefix + _fileView.ClientID + "_FolderTree";
				return FileManagerController.ClientScriptObjectNamePrefix + ClientID;
			}
		}

		[PersistenceMode (PersistenceMode.InnerProperty)]
		[NotifyParentProperty (true)]
		[DefaultValue (null)]
		[Category ("Styles")]
		[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
		internal TreeNodeStyle SelectedNodeStyle {
			get {
				if (selectedNodeStyle == null) {
					selectedNodeStyle = new TreeNodeStyle ();
					if (IsTrackingViewState)
						((IStateManager) selectedNodeStyle).TrackViewState ();
				}
				return selectedNodeStyle;
			}
		}

		Style ControlLinkStyle {
			get {
				if (controlLinkStyle == null) {
					controlLinkStyle = new Style ();
					controlLinkStyle.Font.Underline = false;
					//controlLinkStyle.AlwaysRenderTextDecoration = true;
				}
				return controlLinkStyle;
			}
		}

		Style NodeLinkStyle {
			get {
				if (nodeLinkStyle == null) {
					nodeLinkStyle = new Style ();
				}
				return nodeLinkStyle;
			}
		}

		Style SelectedNodeLinkStyle {
			get {
				if (selectedNodeLinkStyle == null) {
					selectedNodeLinkStyle = new Style ();
				}
				return selectedNodeLinkStyle;
			}
		}

		Style HoverNodeLinkStyle {
			get {
				if (hoverNodeLinkStyle == null) {
					hoverNodeLinkStyle = new Style ();
				}
				return hoverNodeLinkStyle;
			}
		}

		[DefaultValue (false)]
		bool ShowLines {
			get {
				return (bool) (ViewState ["ShowLines"] ?? false);
			}
			set {
				ViewState ["ShowLines"] = value;
			}
		}

		protected override HtmlTextWriterTag TagKey {
			get { return HtmlTextWriterTag.Div; }
		}

		#endregion

		#region  Methods

		protected override void AddAttributesToRender (HtmlTextWriter writer) {
			base.AddAttributesToRender (writer);

			writer.AddStyleAttribute (HtmlTextWriterStyle.Position, "relative");
			writer.AddStyleAttribute (HtmlTextWriterStyle.ZIndex, "20");
			writer.AddStyleAttribute (HtmlTextWriterStyle.Overflow, "auto");
		}

		void InitTreeView () {
			NodeStyle.HorizontalPadding = 4;
			NodeStyle.Height = 20;
			NodeStyle.ForeColor = Color.Black;
			NodeStyle.Font.Names = new string [] { "Tahoma", "Verdana", "Geneva", "Arial", "Helvetica", "sans-serif" };
			NodeStyle.Font.Size = FontUnit.Parse ("11px", null);
            NodeStyle.BorderStyle = BorderStyle.Solid;
            NodeStyle.BorderWidth = 1;
            NodeStyle.BorderColor = Color.Transparent;
            HoverNodeStyle.Font.Underline = true;
			SelectedNodeStyle.Font.Underline = false;
			SelectedNodeStyle.ForeColor = Color.White;
			SelectedNodeStyle.BackColor = Color.FromArgb (0x316AC5);
            SelectedNodeStyle.BorderStyle = BorderStyle.Solid;
            SelectedNodeStyle.BorderWidth = 1;
            SelectedNodeStyle.BorderColor = Color.Transparent;

		}

		void OnSelectedFolderChanged (object sender, EventArgs e) {
			if (SelectedFolderChanged != null)
				SelectedFolderChanged (this, EventArgs.Empty);
		}

		void OnFolderPopulate (FolderNode node) {
			Controller.EnsureDefaults ();

			DirectoryInfo directoryInfo = Controller.ResolveFileManagerItemInfo (node.ValuePath).Directory;
			if (!directoryInfo.Exists)
				return;

			var provider = new DirectoryProvider (directoryInfo, SortMode.Name, SortDirection.Ascending, null);

			foreach (DirectoryInfo dir in provider.GetFileSystemInfos (DirectoryProvider.FileSystemInfosFilter.Directories)) {

                var item = new FileViewItem(directoryInfo, dir, this);
				if (!ShowHiddenFilesAndFolders && item.Hidden)
					continue;
				
				FolderNode treeNode = new FolderNode (this);
				node.ChildNodes.Add (treeNode);
				treeNode.Text = dir.Name;
				treeNode.Hidden = item.Hidden;
				treeNode.ImageUrl = Controller.GetFolderSmallImage (dir);
				if (_fileView != null)
                    treeNode.NavigateUrl = "javascript:" + _fileView.ClientScriptReference + ".Navigate(\"" + treeNode.ValuePath + "\");";
                else
                    treeNode.NavigateUrl = "javascript:" + ClientScriptReference + ".OnSelect(\"" + Controller.GetPathHashCode(treeNode.ValuePath) + "\", \"" + treeNode.ValuePath + "\");";
            }
		}

		void BuildTree () {

			for (int i = 0; i < RootDirectories.Count; i++) {
				RootDirectory rootDir = RootDirectories [i];
				FolderNode treeNode = new FolderNode (this);
				Nodes.Add (treeNode);
				treeNode.ImageUrl = Controller.GetRootFolderSmallImage (rootDir);
				SetText (rootDir, treeNode, i);
				treeNode.Value = PathSeparator + rootDir.TextInternal;
				if (_fileView != null)
                    treeNode.NavigateUrl = "javascript:" + _fileView.ClientScriptReference + ".Navigate(\"" + treeNode.ValuePath + "\");";
                else
                    treeNode.NavigateUrl = "javascript:" + ClientScriptReference + ".OnSelect(\"" + Controller.GetPathHashCode(treeNode.ValuePath) + "\", \"" + treeNode.ValuePath + "\");";
            }

			for (int i = 0; i < RootDirectories.Count; i++)
				ExpandRecursive (Nodes [i], RootDirectories [i].ExpandDepth);

		}

		void SetText (RootDirectory rootDir, FolderNode treeNode, int index) {
			if (rootDir.ShowRootIndex)
				treeNode.Text = String.Format (CultureInfo.InvariantCulture, "{0} ([{1}])", rootDir.TextInternal, index);
			else
				treeNode.Text = rootDir.TextInternal;
		}

		void ExpandRecursive (FolderNode treeNode, int dept) {
			if (dept == 0)
				return;

			treeNode.Expand ();
			foreach (FolderNode child in treeNode.ChildNodes)
				ExpandRecursive (child, dept - 1);
		}

		#endregion

		protected override void TrackViewState () {

			base.TrackViewState ();
			if (hoverNodeStyle != null) {
				((IStateManager) hoverNodeStyle).TrackViewState ();
			}
			if (nodeStyle != null) {
				((IStateManager) nodeStyle).TrackViewState ();
			}
			if (selectedNodeStyle != null) {
				((IStateManager) selectedNodeStyle).TrackViewState ();
			}
		}

		protected override object SaveViewState () {
			object [] states = new object [4];
			states [0] = base.SaveViewState ();
			states [1] = (hoverNodeStyle == null ? null : ((IStateManager) hoverNodeStyle).SaveViewState ());
			states [2] = (nodeStyle == null ? null : ((IStateManager) nodeStyle).SaveViewState ());
			states [3] = (selectedNodeStyle == null ? null : ((IStateManager) selectedNodeStyle).SaveViewState ());

			for (int i = states.Length - 1; i >= 0; i--) {
				if (states [i] != null)
					return states;
			}

			return null;
		}

		protected override void LoadViewState (object savedState) {
			if (savedState == null)
				return;

			object [] states = (object []) savedState;
			base.LoadViewState (states [0]);

			if (states [1] != null)
				((IStateManager) HoverNodeStyle).LoadViewState (states [1]);
			if (states [2] != null)
				((IStateManager) NodeStyle).LoadViewState (states [2]);
			if (states [3] != null)
				((IStateManager) SelectedNodeStyle).LoadViewState (states [3]);
		}

		string callbackResult;

		void ICallbackEventHandler.RaiseCallbackEvent (string eventArgs) {
			int index = eventArgs.IndexOf ('|');
			FolderNode node = new FolderNode (this);
			node.ValuePath = eventArgs.Substring (index + 1);

			node.Expand ();
			int num = node.ChildNodes.Count;
			if (num == 0) {
				callbackResult = "*";
				return;
			}

			ArrayList levelLines = new ArrayList ();
			for (int i = 0; i < index; i++)
				levelLines.Add (eventArgs [i] == '1' ? this : null);

			StringBuilder sb = new StringBuilder ();
			HtmlTextWriter writer = new HtmlTextWriter (new StringWriter (sb));
			EnsureStylesPrepared ();

			for (int n = 0; n < num; n++)
				RenderNode (writer, node.ChildNodes [n], index, levelLines, true, n < num - 1);

			callbackResult = sb.ToString ();
		}

		string ICallbackEventHandler.GetCallbackResult () {
			return callbackResult;
		}

		protected override ControlCollection CreateControlCollection () {
			return new EmptyControlCollection (this);
		}

		protected override void OnPreRender (EventArgs e) {
			base.OnPreRender (e);

			if (Page.Header == null)
				throw new InvalidOperationException ("Using FolderTree requires Page.Header to be non-null (e.g. <head runat=\"server\" />).");

			Page.ClientScript.RegisterClientScriptResource (typeof (FolderTree), "IZ.WebFileManager.resources.FolderTree.js");
			string callbackScript = string.Format ("FolderTree.prototype.PopulateNode = function(argument, clientCallback, context, clientErrorCallback) {{ {0}; }};\n", Page.ClientScript.GetCallbackEventReference ("this._uniqueId", "argument", "clientCallback", "context", "clientErrorCallback", true));
			Page.ClientScript.RegisterClientScriptBlock (typeof (FolderTree), "FolderTree.prototype.PopulateNode", callbackScript, true);

			string folderTree = ClientScriptReference;
			StringBuilder script = new StringBuilder ();
			script.AppendLine ("var " + folderTree + "= new FolderTree ('" + Controller.ClientID + "','" + ClientID + "','" + UniqueID + "','" + GetNodeImageUrl ("Expand") + "','" + GetNodeImageUrl ("Collapse") + "','" + GetNodeImageUrl ("NoExpand") + "');");

			EnsureStylesPrepared ();

			if (selectedNodeStyle != null) {
				script.AppendFormat ("{0}._selectedClass = '{1}';\n", folderTree, _registeredStyleClassNames [SelectedNodeStyle]);
				script.AppendFormat ("{0}._selectedLinkClass = '{1}';\n", folderTree, _registeredStyleClassNames [SelectedNodeLinkStyle]);
			}

			if (hoverNodeStyle != null) {
				script.AppendFormat ("{0}._hoverClass = '{1}';\n", folderTree, _registeredStyleClassNames [HoverNodeStyle]);
				script.AppendFormat ("{0}._hoverLinkClass = '{1}';\n", folderTree, _registeredStyleClassNames [HoverNodeLinkStyle]);
			}
			if (_fileView != null)
				script.AppendLine (folderTree + ".Navigate (['" + String.Join ("','", Controller.GetPathHashCodes (_fileView.CurrentDirectory.FileManagerPath)) + "'],0);");

			Page.ClientScript.RegisterStartupScript (typeof (FolderTree), this.ClientID, script.ToString (), true);

			BuildTree ();
		}

		void EnsureStylesPrepared () {
			if (stylesPrepared)
				return;
			stylesPrepared = true;
			PrepareStyles ();
		}

		private void PrepareStyles () {
			_registeredStyleClassNames = new Dictionary<Style, string> ();

			ControlLinkStyle.Font.CopyFrom (ControlStyle.Font);
			ControlLinkStyle.ForeColor = ControlStyle.ForeColor;
			RegisterStyle (ControlLinkStyle);

			if (nodeStyle != null)
				RegisterStyle (NodeStyle, NodeLinkStyle);

			if (selectedNodeStyle != null)
				RegisterStyle (SelectedNodeStyle, SelectedNodeLinkStyle);

			if (hoverNodeStyle != null)
				RegisterStyle (HoverNodeStyle, HoverNodeLinkStyle);
		}

		void RegisterStyle (Style baseStyle, Style linkStyle) {
			linkStyle.Font.CopyFrom (baseStyle.Font);
			linkStyle.ForeColor = baseStyle.ForeColor;
			linkStyle.BorderStyle = BorderStyle.None;
			baseStyle.Font.ClearDefaults ();
			RegisterStyle (linkStyle);
			RegisterStyle (baseStyle);
		}

		string IncrementStyleClassName () {
			_registeredStylesCounter++;
			return ClientID + "_" + _registeredStylesCounter;
		}

		void RegisterStyle (Style baseStyle) {
			string className = IncrementStyleClassName ();
			Page.Header.StyleSheet.CreateStyleRule (baseStyle, this, "#" + this.ClientID + " ." + className);
			_registeredStyleClassNames [baseStyle] = className;
		}

		string GetBindingKey (string dataMember, int depth) {
			return dataMember + " " + depth;
		}

		protected override void RenderContents (HtmlTextWriter writer) {
			int num = Nodes.Count;
			for (int n = 0; n < num; n++)
				RenderNode (writer, Nodes [n], 0, new ArrayList (), n > 0, n < num - 1);
		}

		void RenderNode (HtmlTextWriter writer, FolderNode node, int level, ArrayList levelLines, bool hasPrevious, bool hasNext) {
			string nodeImage;

			bool hasChildNodes = node.Expanded ? node.ChildNodes.Count > 0 : true;
			if (hasNext)
				levelLines.Add (this);
			else
				levelLines.Add (null);

			writer.AddAttribute ("cellpadding", "0", false);
			writer.AddAttribute ("cellspacing", "0", false);
			writer.AddStyleAttribute ("border-width", "0");
			writer.RenderBeginTag (HtmlTextWriterTag.Table);

			Unit nodeSpacing = GetNodeSpacing (node);

			if (nodeSpacing != Unit.Empty && (node.Depth > 0 || node.Index > 0))
				RenderMenuItemSpacing (writer, nodeSpacing);

			writer.RenderBeginTag (HtmlTextWriterTag.Tr);

			// Vertical lines from previous levels

			nodeImage = GetNodeImageUrl ("I");
			for (int n = 0; n < level; n++) {
				writer.RenderBeginTag (HtmlTextWriterTag.Td);
				writer.AddStyleAttribute ("width", NodeIndent + "px");
				//writer.AddStyleAttribute ("height", "1px");
				writer.RenderBeginTag (HtmlTextWriterTag.Div);
				if (ShowLines && levelLines [n] != null) {
					writer.AddAttribute ("src", nodeImage);
					writer.AddAttribute (HtmlTextWriterAttribute.Alt, "", false);
					writer.RenderBeginTag (HtmlTextWriterTag.Img);
					writer.RenderEndTag ();
				}
				writer.RenderEndTag ();
				writer.RenderEndTag ();	// TD
			}

			// Node image + line

			string shape = String.Empty;

			if (ShowLines) {
				if (hasPrevious && hasNext)
					shape = "T";
				else if (hasPrevious && !hasNext)
					shape = "L";
				else if (!hasPrevious && hasNext)
					shape = "R";
				else
					shape = "Dash";
			}

			if (hasChildNodes) {
				if (node.Expanded)
					shape += "Collapse";
				else
					shape += "Expand";
			}
			else if (!ShowLines)
				shape = "NoExpand";

			if (!String.IsNullOrEmpty (shape)) {
				nodeImage = GetNodeImageUrl (shape);
				writer.RenderBeginTag (HtmlTextWriterTag.Td);	// TD

				writer.AddAttribute (HtmlTextWriterAttribute.Href, GetClientExpandEvent (node));
				writer.RenderBeginTag (HtmlTextWriterTag.A);	// Anchor

				writer.AddAttribute ("alt", String.Empty);

				writer.AddAttribute (HtmlTextWriterAttribute.Id, GetNodeClientId (node, "img"));
				writer.AddAttribute ("src", nodeImage);
				writer.AddStyleAttribute (HtmlTextWriterStyle.BorderWidth, "0");
				writer.RenderBeginTag (HtmlTextWriterTag.Img);
				writer.RenderEndTag ();

				writer.RenderEndTag ();		// Anchor

				writer.RenderEndTag ();		// TD
			}

			writer.RenderBeginTag (HtmlTextWriterTag.Td);
			writer.AddAttribute ("onmouseout", ClientScriptReference + ".UnhoverNode(this)", false);
			writer.AddAttribute ("onmouseover", ClientScriptReference + ".HoverNode(this, event)", false);
			writer.AddAttribute (HtmlTextWriterAttribute.Id, GetNodeClientId (node, null));
			writer.RenderBeginTag (HtmlTextWriterTag.Div);
			writer.AddAttribute ("cellpadding", "0", false);
			writer.AddAttribute ("cellspacing", "0", false);
			writer.AddStyleAttribute ("border-width", "0");
			writer.RenderBeginTag (HtmlTextWriterTag.Table);
			writer.RenderBeginTag (HtmlTextWriterTag.Tr);

			// Node icon

			string imageUrl = node.ImageUrl.Length > 0 ? ResolveClientUrl (node.ImageUrl) : null;

			if (!String.IsNullOrEmpty (imageUrl)) {
				if (node.Hidden)
					Controller.HiddenItemStyle.AddAttributesToRender (writer);
				writer.RenderBeginTag (HtmlTextWriterTag.Td);	// TD
				BeginNodeTag (writer, node);
				writer.AddAttribute ("src", imageUrl);
				writer.AddStyleAttribute (HtmlTextWriterStyle.BorderWidth, "0");
				writer.AddAttribute ("alt", String.Empty);
				writer.RenderBeginTag (HtmlTextWriterTag.Img);
				writer.RenderEndTag ();	// IMG
				writer.RenderEndTag ();	// node tag
				writer.RenderEndTag ();	// TD
			}

			writer.AddStyleAttribute ("white-space", "nowrap");
			AddNodeStyle (writer, node, level);

			writer.AddAttribute ("nodepath", GetNodePath (node, levelLines));
			writer.AddAttribute (HtmlTextWriterAttribute.Id, GetNodeClientId (node, "node"));

			writer.RenderBeginTag (HtmlTextWriterTag.Td);	// TD

			AddNodeLinkStyle (writer, node, level);
			BeginNodeTag (writer, node);
			writer.Write (node.Text);
			writer.RenderEndTag ();	// node tag

			writer.RenderEndTag ();	// TD

			writer.RenderEndTag (); // TR
			writer.RenderEndTag (); // TABLE
			writer.RenderEndTag (); // DIV
			writer.RenderEndTag (); // TD

			writer.RenderEndTag ();	// TR

			if (nodeSpacing != Unit.Empty)
				RenderMenuItemSpacing (writer, nodeSpacing);

			writer.RenderEndTag ();	// TABLE

			// Children

			if (!node.Expanded || node.ChildNodes.Count == 0)
				writer.AddStyleAttribute ("display", "none");
			else
				writer.AddStyleAttribute ("display", "block");
			writer.AddAttribute (HtmlTextWriterAttribute.Id, GetNodeClientId (node, "span"));
			writer.RenderBeginTag (HtmlTextWriterTag.Span);

			if (node.Expanded) {
				//AddChildrenPadding (writer, node);
				int num = node.ChildNodes.Count;
				if (num == 0)
					writer.Write ("*");
				else
					for (int n = 0; n < num; n++)
						RenderNode (writer, node.ChildNodes [n], level + 1, levelLines, true, n < num - 1);
				//if (hasNext)
				//    AddChildrenPadding (writer, node);
			}
			writer.RenderEndTag ();	// SPAN

			levelLines.RemoveAt (level);
		}

		//private void AddChildrenPadding (HtmlTextWriter writer, FolderNode node) {
		//    int level = node.Depth;
		//    Unit cnp = Unit.Empty;

		//    if (cnp.IsEmpty && nodeStyle != null)
		//        cnp = nodeStyle.ChildNodesPadding;

		//    double value;
		//    if (cnp.IsEmpty || (value = cnp.Value) == 0 || cnp.Type != UnitType.Pixel)
		//        return;

		//    writer.RenderBeginTag (HtmlTextWriterTag.Table);
		//    writer.AddAttribute ("height", ((int) value).ToString (), false);
		//    writer.RenderBeginTag (HtmlTextWriterTag.Tr);
		//    writer.RenderBeginTag (HtmlTextWriterTag.Td);
		//    writer.RenderEndTag (); // td
		//    writer.RenderEndTag (); // tr
		//    writer.RenderEndTag (); // table
		//}

		private void RenderMenuItemSpacing (HtmlTextWriter writer, Unit itemSpacing) {
			writer.AddStyleAttribute ("height", itemSpacing.ToString ());
			writer.RenderBeginTag (HtmlTextWriterTag.Tr);
			writer.RenderBeginTag (HtmlTextWriterTag.Td);
			writer.RenderEndTag ();
			writer.RenderEndTag ();
		}

		private Unit GetNodeSpacing (FolderNode node) {
			if (node.Selected && selectedNodeStyle != null && selectedNodeStyle.NodeSpacing != Unit.Empty)
				return selectedNodeStyle.NodeSpacing;
			if (nodeStyle != null)
				return nodeStyle.NodeSpacing;
			else
				return Unit.Empty;
		}

		void AddNodeStyle (HtmlTextWriter writer, FolderNode node, int level) {
			StringBuilder sb = new StringBuilder ();
			if (nodeStyle != null) {
				sb.Append (" ");
				sb.Append (nodeStyle.CssClass);
				sb.Append (" ");
				sb.Append (_registeredStyleClassNames [nodeStyle]);
			}
			if (node.ValuePath == SelectedPath && selectedNodeStyle != null) {
				sb.Append (" ");
				sb.Append (selectedNodeStyle.CssClass);
				sb.Append (" ");
				sb.Append (_registeredStyleClassNames [selectedNodeStyle]);
			}
			writer.AddAttribute (HtmlTextWriterAttribute.Class, sb.ToString ());
		}

		void AddNodeLinkStyle (HtmlTextWriter writer, FolderNode node, int level) {
			StringBuilder sb = new StringBuilder ();
			sb.Append (" ");
			sb.Append (_registeredStyleClassNames [ControlLinkStyle]);

			if (nodeStyle != null) {
				sb.Append (" ");
				sb.Append (nodeLinkStyle.CssClass);
				sb.Append (" ");
				sb.Append (_registeredStyleClassNames [nodeLinkStyle]);
			}
			if (node.ValuePath == SelectedPath && selectedNodeStyle != null) {
				sb.Append (" ");
				sb.Append (selectedNodeLinkStyle.CssClass);
				sb.Append (" ");
				sb.Append (_registeredStyleClassNames [selectedNodeLinkStyle]);
			}
			writer.AddAttribute (HtmlTextWriterAttribute.Class, sb.ToString ());
		}

		void BeginNodeTag (HtmlTextWriter writer, FolderNode node) {
			if (node.ToolTip.Length > 0)
				writer.AddAttribute ("title", node.ToolTip);

			if (!String.IsNullOrEmpty (node.NavigateUrl)) {
				string navUrl = ResolveClientUrl (node.NavigateUrl);
				writer.AddAttribute ("href", navUrl);
				writer.RenderBeginTag (HtmlTextWriterTag.A);
			}
			else
				writer.RenderBeginTag (HtmlTextWriterTag.Span);
		}

		string GetNodeClientId (FolderNode node, string sufix) {
			return ClientID + "_" + node.ClientID + (sufix != null ? "_" + sufix : "");
		}

		string GetNodeImageUrl (string shape) {
			if (!ShowLines) {
				if (shape == "Expand") {
					if (!String.IsNullOrEmpty (ExpandImageUrl))
						return ResolveClientUrl (ExpandImageUrl);
				}
				else if (shape == "Collapse") {
					if (!String.IsNullOrEmpty (CollapseImageUrl))
						return ResolveClientUrl (CollapseImageUrl);
				}
				else if (shape == "NoExpand") {
					if (!String.IsNullOrEmpty (NoExpandImageUrl))
						return ResolveClientUrl (NoExpandImageUrl);
				}
			}
			return Page.ClientScript.GetWebResourceUrl (typeof (Page), "TreeView_Default_" + shape + ".gif");
		}

		string GetNodeIconUrl (string icon) {
			return Page.ClientScript.GetWebResourceUrl (typeof (TreeView), icon + ".gif");
		}

		string GetClientExpandEvent (FolderNode node) {
			return "javascript:" + ClientScriptReference + ".ToggleExpand (\"" + node.ClientID + "\")";
		}

		string GetNodePath (FolderNode node, ArrayList levelLines) {
			StringBuilder arg = new StringBuilder ();
			for (int i = 0; i < levelLines.Count; i++)
				if (levelLines [i] == null)
					arg.Append ('0');
				else
					arg.Append ('1');
			arg.Append ('|');
			arg.Append (node.ValuePath);
			return arg.ToString ();
		}

		sealed class FolderNode
		{
			Collection<FolderNode> _nodes;
			readonly FolderTree _owner;
			FolderNode _parent;
			int _index = -1;
			int _depth = -1;
			string _clientId;
			string _valuePath;
			string _imageUrl;
			string _navigateUrl;
			string _value;
			string _text;
			string _tooltip;
			bool _expanded;
			bool _selected;
			bool _hidden;

			public FolderNode (FolderTree owner) {
				_owner = owner;
			}

			[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
			[Browsable (false)]
			public int Depth {
				get {
					if (_depth != -1)
						return _depth;
					_depth = 0;
					FolderNode node = _parent;
					while (node != null) {
						_depth++;
						node = node.Parent;
					}
					return _depth;
				}
			}

			public Collection<FolderNode> ChildNodes {
				get {
					if (_nodes == null) {
						_nodes = new FolderNodeCollection (this);
					}
					return _nodes;
				}
			}

			public bool Expanded {
				get { return _expanded; }
			}
			
			public bool Hidden {
				get { return _hidden; }
				set { _hidden = value; }
			}

			public string ImageUrl {
				get { return _imageUrl ?? String.Empty; }
				set { _imageUrl = value; }
			}

			public string NavigateUrl {
				get { return _navigateUrl ?? String.Empty; }
				set { _navigateUrl = value; }
			}

			public string Text {
				get { return _text ?? (_value ?? String.Empty); }
				set { _text = value; }
			}

			public string ToolTip {
				get { return _tooltip ?? String.Empty; }
				set { _tooltip = value; }
			}

			public string Value {
				get { return _value ?? (_text ?? String.Empty); }
				set { _value = value; }
			}

			[DefaultValue (false)]
			public bool Selected {
				get { return _selected; }
				set { _selected = value; }
			}

			[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
			[Browsable (false)]
			public FolderNode Parent {
				get { return _parent; }
			}

			[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
			[Browsable (false)]
			public string ValuePath {
				get {
					if (_owner == null)
						return Value;
					if (_valuePath == null) {
						StringBuilder sb = new StringBuilder (Value);
						FolderNode node = Parent;
						if (node != null) {
							sb.Insert (0, _owner.PathSeparator);
							sb.Insert (0, node.ValuePath);
							node = node.Parent;
						}
						_valuePath = sb.ToString ();
					}
					return _valuePath;
				}
				set {
					_valuePath = value;
				}
			}

			internal int Index {
				get {
					if (_index < 0) {
						_index = 0;
						if (Parent != null)
							_index = Parent.ChildNodes.IndexOf (this);
					}
					return _index;
				}
			}

			internal void SetParent (FolderNode node) {
				_parent = node;
				_index = -1;
				_depth = -1;
				_clientId = null;
				_valuePath = null;
			}

			internal string ClientID {
				get {
					if (_clientId == null) {
						_clientId = _owner.Controller.GetPathHashCode (ValuePath);
					}
					return _clientId;
				}
			}

			public void Expand () {
				if (Expanded)
					return;
				_expanded = true;
				_owner.OnFolderPopulate (this);
			}
		}

		class FolderNodeCollection : Collection<FolderNode>
		{
			readonly FolderNode _owner;

			public FolderNodeCollection (FolderNode owner) {
				_owner = owner;
			}

			protected override void InsertItem (int index, FolderNode item) {
				base.InsertItem (index, item);
				item.SetParent (_owner);
			}

			protected override void SetItem (int index, FolderNode item) {
				base.SetItem (index, item);
				item.SetParent (_owner);
			}
		}

	}
}
